﻿using Destiny.Core.Flow.Extensions;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;

namespace Destiny.Core.Flow.Identity
{
    public abstract class RoleStoreBase<TRole, TRoleKey, TRoleClaim>
        : IQueryableRoleStore<TRole>,
          IRoleClaimStore<TRole>
        where TRole : RoleBase<TRoleKey>
        where TRoleClaim : RoleClaimBase<TRoleKey>, new()
        where TRoleKey : IEquatable<TRoleKey>
    {
        private readonly IEFCoreRepository<TRole, TRoleKey> _roleRepository;
        private readonly IEFCoreRepository<TRoleClaim, Guid> _roleClaimRepository;
        private bool _disposed;

        protected RoleStoreBase(
            IEFCoreRepository<TRole, TRoleKey> roleRepository,
            IEFCoreRepository<TRoleClaim, Guid> roleClaimRepository)
        {
            _roleRepository = roleRepository;
            _roleClaimRepository = roleClaimRepository;
        }

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting
        /// unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _disposed = true;
        }

        #endregion Implementation of IDisposable

        #region Implementation of IQueryableRoleStore<TRole>

        /// <summary>
        /// Returns an <see cref="T:System.Linq.IQueryable`1"/> collection of roles.
        /// </summary>
        /// <value>An <see cref="T:System.Linq.IQueryable`1"/> collection of roles.</value>
        public IQueryable<TRole> Roles => _roleRepository.TrackEntities;

        #endregion Implementation of IQueryableRoleStore<TRole>

        #region Implementation of IRoleStore<TRole>

        /// <summary>
        /// Creates a new role in a store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role to create in the store.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that represents the <see
        /// cref="T:Microsoft.AspNetCore.Identity.IdentityResult"/> of the asynchronous query.
        /// </returns>
        public async Task<IdentityResult> CreateAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            //检查
            await _roleRepository.InsertAsync(role);
            return IdentityResult.Success;
        }

        /// <summary>
        /// Updates a role in a store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role to update in the store.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that represents the <see
        /// cref="T:Microsoft.AspNetCore.Identity.IdentityResult"/> of the asynchronous query.
        /// </returns>
        public async Task<IdentityResult> UpdateAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            await _roleRepository.UpdateAsync(role);
            return IdentityResult.Success;
        }

        /// <summary>
        /// Deletes a role from the store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role to delete from the store.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that represents the <see
        /// cref="T:Microsoft.AspNetCore.Identity.IdentityResult"/> of the asynchronous query.
        /// </returns>
        public async Task<IdentityResult> DeleteAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (role.IsAdmin)
            {
                return new IdentityResult().Failed($"角色“{role.Name}”是管理员，不能删除");
            }
            await _roleRepository.DeleteAsync(role);
            return IdentityResult.Success;
        }

        /// <summary>
        /// Gets the ID for a role from the store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose ID should be returned.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that contains the ID of the role.
        /// </returns>
        public Task<string> GetRoleIdAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return Task.FromResult(ConvertIdToString(role.Id));
        }

        /// <summary>
        /// Gets the name of a role from the store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose name should be returned.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that contains the name of the role.
        /// </returns>
        public Task<string> GetRoleNameAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return Task.FromResult(role.Name);
        }

        /// <summary>
        /// Sets the name of a role in the store as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose name should be set.</param>
        /// <param name="roleName">The name of the role.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/> that represents the asynchronous operation.
        /// </returns>
        public Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            role.Name = roleName;
            return Task.CompletedTask;
        }

        /// <summary>
        /// Get a role's normalized name as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose normalized name should be retrieved.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that contains the name of the role.
        /// </returns>
        public Task<string> GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return Task.FromResult(role.NormalizedName);
        }

        /// <summary>
        /// Set a role's normalized name as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose normalized name should be set.</param>
        /// <param name="normalizedName">The normalized name to set</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/> that represents the asynchronous operation.
        /// </returns>
        public Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            role.NormalizedName = normalizedName;
            return Task.CompletedTask;
        }

        /// <summary>
        /// Finds the role who has the specified ID as an asynchronous operation.
        /// </summary>
        /// <param name="roleId">The role ID to look for.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that result of the look up.
        /// </returns>
        public Task<TRole> FindByIdAsync(string roleId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            TRoleKey id = ConvertIdFromString(roleId);
            return Task.FromResult(_roleRepository.TrackEntities.FirstOrDefault(m => m.Id.Equals(id)));
        }

        /// <summary>
        /// Finds the role who has the specified normalized name as an asynchronous operation.
        /// </summary>
        /// <param name="normalizedRoleName">The normalized role name to look for.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that result of the look up.
        /// </returns>
        public Task<TRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            return Task.FromResult(_roleRepository.TrackEntities.FirstOrDefault(m => m.NormalizedName == normalizedRoleName));
        }

        #endregion Implementation of IRoleStore<TRole>

        #region Implementation of IRoleClaimStore<TRole>

        /// <summary>
        /// Gets a list of <see cref="T:System.Security.Claims.Claim"/> s to be belonging to the
        /// specified <paramref name="role"/> as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role whose claims to retrieve.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>
        /// A <see cref="T:System.Threading.Tasks.Task`1"/> that represents the result of the
        /// asynchronous query, a list of <see cref="T:System.Security.Claims.Claim"/> s.
        /// </returns>
        public Task<IList<Claim>> GetClaimsAsync(TRole role, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            IList<Claim> list = _roleClaimRepository.Entities.Where(m => m.RoleId.Equals(role.Id)).Select(n => new Claim(n.ClaimType, n.ClaimValue)).ToList();
            return Task.FromResult(list);
        }

        /// <summary>
        /// Add a new claim to a role as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role to add a claim to.</param>
        /// <param name="claim">The <see cref="T:System.Security.Claims.Claim"/> to add.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public async Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            TRoleClaim roleClaim = new TRoleClaim() { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value };
            await _roleClaimRepository.InsertAsync(roleClaim);
        }

        /// <summary>
        /// Remove a claim from a role as an asynchronous operation.
        /// </summary>
        /// <param name="role">The role to remove the claim from.</param>
        /// <param name="claim">The <see cref="T:System.Security.Claims.Claim"/> to remove.</param>
        /// <param name="cancellationToken">
        /// The <see cref="T:System.Threading.CancellationToken"/> used to propagate notifications
        /// that the operation should be canceled.
        /// </param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            await _roleClaimRepository.DeleteBatchAsync(m => m.RoleId.Equals(role.Id) && m.ClaimValue == claim.Type && m.ClaimValue == claim.Value);
        }

        #endregion Implementation of IRoleClaimStore<TRole>

        /// <summary>
        /// Converts the provided <paramref name="id"/> to a strongly typed key object.
        /// </summary>
        /// <param name="id">The id to convert.</param>
        /// <returns>
        /// An instance of <typeparamref name="TRoleKey"/> representing the provided <paramref name="id"/>.
        /// </returns>
        public virtual TRoleKey ConvertIdFromString(string id)
        {
            if (id == null)
            {
                return default(TRoleKey);
            }
            return (TRoleKey)TypeDescriptor.GetConverter(typeof(TRoleKey)).ConvertFromInvariantString(id);
        }

        /// <summary>
        /// Converts the provided <paramref name="id"/> to its string representation.
        /// </summary>
        /// <param name="id">The id to convert.</param>
        /// <returns>An <see cref="string"/> representation of the provided <paramref name="id"/>.</returns>
        public virtual string ConvertIdToString(TRoleKey id)
        {
            if (id.Equals(default(TRoleKey)))
            {
                return null;
            }
            return id.ToString();
        }

        /// <summary>
        /// 如果已释放，则抛出异常
        /// </summary>
        protected void ThrowIfDisposed()
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }
    }
}